package de.uni_potsdam.hpi.bpt.ai.example;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;

import org.json.JSONException;

import de.uni_potsdam.hpi.bpt.ai.collection.Model;
import de.uni_potsdam.hpi.bpt.ai.diagram.Diagram;
import de.uni_potsdam.hpi.bpt.ai.util.Aggregate;
import de.uni_potsdam.hpi.bpt.ai.util.BpmMetrics;

public class BpmMeasures extends Experiment {
	@SuppressWarnings("unchecked")
	protected static Map<String, Aggregate<Double>> readFromFile(String filename) throws ClassNotFoundException, IOException {
		
		// Read from disk using FileInputStream
		FileInputStream f_in = new 
			FileInputStream(STAT_PATH + File.separator + filename);

		// Read object using ObjectInputStream
		ObjectInputStream obj_in = 
			new ObjectInputStream (f_in);

		// Read an object
		Object obj = obj_in.readObject();

		if (obj instanceof Map<?,?>) {
			return (Map<String, Aggregate<Double>>) obj;
		}
		
		return null;
	}
	
	protected static void writeToFile(String filename, Map<String, Aggregate<Double>> object) throws IOException {
		// Write to disk with FileOutputStream
		
		File f = new File(STAT_PATH + File.separator + filename);
		if (!f.exists()) {
			f.createNewFile();
		}
		
		FileOutputStream f_out = new 
			FileOutputStream(STAT_PATH + File.separator + filename);

		// Write object with ObjectOutputStream
		ObjectOutputStream obj_out = new
			ObjectOutputStream (f_out);

		// Write object out to disk
		obj_out.writeObject (object);
	}
	
	protected static Map<String, Double> calculateMeasures(Diagram diagram) {

		Map<String, Double> measures = new HashMap<String, Double>();
		System.out.print("s.");		measures.put(SIZE, (double) BpmMetrics.size(diagram));				
		System.out.print("sa.");	measures.put(SIZE_ARCS, (double) BpmMetrics.sizeArcs(diagram));		
		System.out.print("dia.");	measures.put(DIAMETER, (double) BpmMetrics.diameter(diagram));		
		System.out.print("den.");	measures.put(DENSITY, BpmMetrics.density(diagram));					
		System.out.print("cnc.");	measures.put(CNC, BpmMetrics.cnc(diagram));							
		System.out.print("adc.");	measures.put(AVG_DC, BpmMetrics.avgDc(diagram));					
		System.out.print("mdc.");	measures.put(MAX_DC, BpmMetrics.maxDc(diagram));					
		System.out.print("cyc.");	measures.put(CYC, BpmMetrics.cyc(diagram));							
		System.out.print("ts.");	measures.put(TS, (double) BpmMetrics.ts(diagram));					
		System.out.print("ch.");	measures.put(CH, BpmMetrics.ch(diagram));							
	
		return measures;
	}
	
	public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException, JSONException, IOException {
	
		BpmMeasures m = new BpmMeasures();
		m.run(); // either loads or calculates the data
		m.PrintLatex();
	
	}

	private void printMeasures() {
		
		System.out.println("BPMN");
		Map<Double, Integer> d = this.bpmnData.get(SIZE).cluster();
		for (double i=0; i<= this.bpmnData.get(SIZE).max(); i++) {
			System.out.println((int)i+";"+(null != d.get(i) ? d.get(i) : 0));
		}
		
		System.out.println("EPC");
		d = this.epcData.get(SIZE).cluster();
		for (double i =0; i<= this.epcData.get(SIZE).max(); i++) {
			System.out.println((int)i+";"+(null != d.get(i) ? d.get(i) : 0));
		}
		
		System.exit(1);
		
		System.out.println();
		System.out.println(" data |  #models | min size | max size | avg size | med size | .8q size ");
		
		PrintWriter pw = new PrintWriter(System.out);
		Formatter formatter = new Formatter(pw, Locale.GERMAN);
		formatter.format("%5s ", "epc");
		formatter.format("%10d ",   this.epcData.get(SIZE).size());
		formatter.format("%10.0f ", this.epcData.get(SIZE).min());
		formatter.format("%10.0f ", this.epcData.get(SIZE).max());
		formatter.format("%10.2f ", this.epcData.get(SIZE).avg());
		formatter.format("%10.2f ", this.epcData.get(SIZE).median());
		formatter.format("%10.2f ", this.epcData.get(SIZE).quantile(0.8));
		
		pw.write("\n"); pw.flush();
		
		formatter.format("%5s ", "bpmn");
		formatter.format("%10d ",   this.bpmnData.get(SIZE).size());
		formatter.format("%10.0f ", this.bpmnData.get(SIZE).min());
		formatter.format("%10.0f ", this.bpmnData.get(SIZE).max());
		formatter.format("%10.2f ", this.bpmnData.get(SIZE).avg());
		formatter.format("%10.2f ", this.bpmnData.get(SIZE).median());
		formatter.format("%10.2f ", this.bpmnData.get(SIZE).quantile(0.8));
		
		pw.write("\n"); pw.flush();

		formatter.format("%5s ", "all");
		formatter.format("%10d ",   this.data.get(SIZE).size());
		formatter.format("%10.0f ", this.data.get(SIZE).min());
		formatter.format("%10.0f ", this.data.get(SIZE).max());
		formatter.format("%10.2f ", this.data.get(SIZE).avg());
		formatter.format("%10.2f ", this.data.get(SIZE).median());
		formatter.format("%10.2f ", this.data.get(SIZE).quantile(0.8));
		
		pw.flush();
		pw.close();
	}
	
	String[] ms = new String[]{
		SIZE,
		DIAMETER,
		DENSITY,
		CNC,
		AVG_DC,
		MAX_DC,
		CH,
		CYC,
		TS,
	};
	
	protected String getLatexAvg(Map<String, Aggregate<Double>> data, String sep, double precision) {
		String res = "";
		for (int i=0; i<ms.length;i++){ 
			String m = ms[i];
			res += Math.round(precision*(double)data.get(m).avg())/precision + (i == ms.length-1 ? "" : sep);
		}
		return res;
	}
	
	protected String getLatexMin(Map<String, Aggregate<Double>> data, String sep, double precision) {
		String res = "";
		for (int i=0; i<ms.length;i++){ 
			String m = ms[i];
			res += Math.round(precision*(double)data.get(m).min())/precision + (i == ms.length-1 ? "" : sep);
		}
		return res;
	}
	
	protected String getLatexMax(Map<String, Aggregate<Double>> data, String sep, double precision) {
		String res = "";
		for (int i=0; i<ms.length;i++){ 
			String m = ms[i];
			res += Math.round(precision*(double)data.get(m).max())/precision + (i == ms.length-1 ? "" : sep);
		}
		return res;
	}
	
	protected String getLatexMedian(Map<String, Aggregate<Double>> data, String sep, double precision) {
		String res = "";
		for (int i=0; i<ms.length;i++){ 
			String m = ms[i];
			res += Math.round(precision*(double)data.get(m).median())/precision + (i == ms.length-1 ? "" : sep);
		}
		return res;
	}
	
	protected String getLatexUpperQ(Map<String, Aggregate<Double>> data, String sep, double precision) {
		String res = "";
		for (int i=0; i<ms.length;i++){ 
			String m = ms[i];
			res += Math.round(precision*(double)data.get(m).quantile(.75))/precision + (i == ms.length-1 ? "" : sep);
		}
		return res;
	}
	
	protected String getLatexLowerQ(Map<String, Aggregate<Double>> data, String sep, double precision) {
		String res = "";
		for (int i=0; i<ms.length;i++){ 
			String m = ms[i];
			res += Math.round(precision*(double)data.get(m).quantile(.25))/precision + (i == ms.length-1 ? "" : sep);
		}
		return res;
	}
	
	protected void PrintLatex(){
		
		String result = "\\begin{table*}[tb] \n"+
		"\\caption{Metrics for the BPMN and EPC models of the BPMAI collection} \n"+
		"\\label{tab:metrics} \n"+
		"\\footnotesize \n"+
		"%@{\\hspace{0.02\\textwidth} \n"+
		"\\begin{center} \n"+
		"	\\begin{tabular} {l  r r r r r r r r r } \n"+
		"	\\toprule \n"+
		"   & \\multicolumn{2}{c}{Size }  & \\multicolumn{4}{c}{Density} & \\multicolumn{1}{c}{Rout. Div.} & \\multicolumn{1}{c}{Cyclicity} & \\multicolumn{1}{c}{Concurrency} \\\\ \n"+
		"   & \\multicolumn{1}{c}{\\emph{NN}} & \\multicolumn{1}{c}{\\emph{Diam}} & \\multicolumn{1}{c}{\\emph{Dens}} & \\multicolumn{1}{c}{\\emph{CNC}} & \\multicolumn{1}{c}{\\emph{AvgDR}} & \\multicolumn{1}{c}{\\emph{MaxDR}} & \\multicolumn{1}{c}{\\emph{RH}} & \\multicolumn{1}{c}{\\emph{CYC}} & \\multicolumn{1}{c}{\\emph{TS}}\\\\ \n"+
		"  \\midrule \n"+
		"	\\multicolumn{8}{l}{Results for the BPMN models:}\\\\ \n"+
		"   Avg & "+getLatexAvg(bpmnData, " & ", 100)+"\\\\ \n"+
		"%   Min & "+getLatexMin(bpmnData, " & ", 100)+"\\\\ \n"+
		"   Max & "+getLatexMax(bpmnData, " & ", 100)+"\\\\ \n"+
		"   Median & "+getLatexMedian(bpmnData, " & ", 100)+"\\\\ \n"+
		"   Upper Q & "+getLatexUpperQ(bpmnData, " & ", 100)+"\\\\ \n"+
		"   Lower Q & "+getLatexLowerQ(bpmnData, " & ", 100)+"\\\\ \n"+
		"  \\midrule \n"+
		"	\\multicolumn{8}{l}{Results for the EPC models:}\\\\ \n"+
		"   Avg & "+getLatexAvg(epcData, " & ", 100)+"\\\\ \n"+
		"%   Min & "+getLatexMin(epcData, " & ", 100)+"\\\\ \n"+
		"   Max & "+getLatexMax(epcData, " & ", 100)+"\\\\ \n"+
		"   Median & "+getLatexMedian(epcData, " & ", 100)+"\\\\ \n"+
		"   Upper Q & "+getLatexUpperQ(epcData, " & ", 100)+"\\\\ \n"+
		"   Lower Q & "+getLatexLowerQ(epcData, " & ", 100)+"\\\\ \n"+
		"	\\bottomrule \n"+
		"	\\end{tabular} \n"+
		"\\end{center} \n"+
		" \\end{table*}";
		
		System.out.println(result);
	}

	protected Map<String, Aggregate<Double>> data;
	protected Map<String, Aggregate<Double>> epcData;
	protected Map<String, Aggregate<Double>> bpmnData;
	
	public BpmMeasures() {
		this.data = new HashMap<String, Aggregate<Double>>();
		this.epcData = new HashMap<String, Aggregate<Double>>();
		this.bpmnData = new HashMap<String, Aggregate<Double>>();
	}
	
	@Override
	protected void examineModel(Model model, Diagram diagram, int index) {
		String namespace = diagram.getStencilset().getNamespace();
		
		Map<String, Aggregate<Double>> chooseData = null;
		
		if ("http://b3mn.org/stencilset/bpmn2.0#".equals(namespace)) {
			chooseData = this.bpmnData;
		}
		else if("http://b3mn.org/stencilset/epc#".equals(namespace)) {
			chooseData = this.epcData;
		}	
		
		if (null != chooseData) {
			Map<String, Double> measures = null;
			
			try {
				String[] ns = namespace.split("/");
				
				System.out.print(String.format("%5d - %-18s | %8s | ", 
						index,  
						(model.getName().length() > 18 ? (model.getName().trim().substring(0, Math.min(15, model.getName().length())) + "...") : model.getName()),
						ns[ns.length-1]));
				measures = calculateMeasures(diagram);
				System.out.println();
				
				for (String key : measures.keySet()) {
					if (!chooseData.containsKey(key)) {
						chooseData.put(key, new Aggregate<Double>());
					}
					chooseData.get(key).add(measures.get(key));
					
					if (!this.data.containsKey(key)) {
						this.data.put(key, new Aggregate<Double>());
					}
					
					if (Double.isNaN(measures.get(key))) {
						System.out.println("NAN "+key);
					}
					
					if("http://b3mn.org/stencilset/epc#".equals(namespace) && key == SIZE && measures.get(key) >= 120) {
						System.out.println(SIZE + "= " + measures.get(key) +"\n");
					}
					
					if (key == DENSITY && measures.get(key) >= 0.5) {
						System.out.println(model.getId());
						System.out.println(DENSITY + "= " + measures.get(key) +"\n");
					}
					
					
					this.data.get(key).add(measures.get(key));
				}
			}
			catch(Exception e) {
				System.out.println(" ERROR");
				e.printStackTrace();
			}
		}
	}
	
	protected void run() throws UnsupportedEncodingException, FileNotFoundException, JSONException, IOException {
		// try to load data
		try {
			System.out.print("Reading data from file .. ");
			
			data = readFromFile("all.data");
			epcData = readFromFile("epc.data");
			bpmnData = readFromFile("bpmn.data");
			
			System.out.println("done.");
		}
		catch(FileNotFoundException fn) {
			
			System.out.println("failed.");
			
			// if data cannot be loaded recalculate it
			data = new HashMap<String, Aggregate<Double>>();
			epcData = new HashMap<String, Aggregate<Double>>();
			bpmnData = new HashMap<String, Aggregate<Double>>();
			
			Date t0 = new Date();
			System.out.println("Calculating data .. (" + DateFormat.getDateTimeInstance().format(t0) + ")");
			
			this.eachModel(new HashSet<String>(Arrays.asList(new String[]{
					NS_BPMN,
					NS_EPC
			})));
			
			Date t1 = new Date();
			System.out.println("Calculating data .. done. (" + DateFormat.getDateTimeInstance().format(t1) + ")");
			System.out.println("  total calculation time (s): " + Math.round((double)(t1.getTime() - t0.getTime())/1000.0));
			System.out.println("  avg calculation time per model (ms): " + Math.round((double)(t1.getTime() - t0.getTime())/(double)data.get(SIZE).size()));
			
			// try to save the collected data
			try {
				
				System.out.print("Write data to file ..");
				writeToFile("all.data", data);
				writeToFile("bpmn.data", bpmnData);
				writeToFile("epc.data", epcData);
				System.out.println("done.");
			}
			catch(Exception e) {
				System.out.println("failed.");
				e.printStackTrace();
			}
		}
		catch(Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}
	}
}
